package com.imeth.base.web;

import com.google.common.collect.Lists;
import com.imeth.base.model.Receipt;
import com.imeth.base.model.Result;
import com.imeth.base.util.DateUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * Created by peter on 12/24/2015.
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class BaseController {
    /**
     * 验证Bean实例对象
     */
    @Autowired
    protected Validator validator;

    /**
     * 服务端参数有效性验证
     *
     * @param object
     *            验证的实体对象
     * @param groups
     *            验证组
     * @return 验证成功：返回true；严重失败：将错误信息添加到 message 中
     */
    protected boolean beanValidator(Result<Receipt> model, Object object, Class<?>... groups) {
        try {
            validateWithException(validator, object, groups);
        } catch (ConstraintViolationException ex) {
            List<String> list = extractPropertyAndMessageAsList(ex, ": ");
            list.add(0, "数据验证失败：");
            addMessage(model, list.toArray(new String[] {}));
            return false;
        }
        return true;
    }

    /**
     * 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException,
     * 而不是返回constraintViolations.
     */
    public static void validateWithException(Validator validator, Object object, Class<?>... groups)
            throws ConstraintViolationException {
        Set constraintViolations = validator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            throw new ConstraintViolationException(constraintViolations);
        }
    }

    /**
     * 转换ConstraintViolationException的Set<ConstraintViolations>为List<
     * propertyPath + separator + message>.
     *
     * @param e
     *            ConstraintViolationException
     * @param separator
     *            分隔符
     * @return List<String>, String内容为propertyPath + separator + message
     */
    public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
        return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
    }

    /**
     * 转换Set<ConstraintViolation>为List<propertyPath + separator + message>.
     * @param constraintViolations
     * @param separator
     * @return
     */
    public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations, String separator) {
        List<String> errorMessages = Lists.newArrayList();
        for (ConstraintViolation violation : constraintViolations) {
            errorMessages.add(violation.getMessage());
        }
        return errorMessages;
    }

    /**
     * 添加Model消息
     *
     * @param messages
     *            消息
     */
    protected void addMessage(Result<Receipt> model, String... messages) {
        StringBuilder sb = new StringBuilder();
        sb.append(messages[0]);

        for (int i = 1; i < messages.length; i++) {
            sb.append(messages[i]);
            if (i != messages.length - 1) {
                sb.append(", ");
            }
        }

        model.setMessage(sb.toString());
        model.setStatus(500);
    }

    /**
     * 初始化数据绑定 1. 将所有传递进来的String进行HTML编码，防止XSS攻击 2. 将字段中Date类型转换为String类型
     */
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        // String类型转换，将所有传递进来的String进行HTML编码，防止XSS攻击
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(text == null ? null : StringEscapeUtils
                        .escapeHtml4(text.trim()));
            }

            @Override
            public String getAsText() {
                Object value = getValue();
                return value != null ? value.toString() : "";
            }
        });

        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(DateUtils.parseDate(text));
            }
        });
    }
}
